package com.jourwon.spring.boot.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.RetryListener;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;

/**
 * RetryTemplate配置
 *
 * @author JourWon
 * @date 2022/6/19
 */
@Slf4j
@EnableRetry
@Configuration
public class RetryTemplateConfig {

    @Bean
    public RetryTemplate retryTemplate() {
        RetryTemplate retryTemplate = new RetryTemplate();

        // AlwaysRetryPolicy：允许无限重试，直到成功，可能会导致死循环
        // CircuitBreakerRetryPolicy：有熔断功能的重试策略，需设置3个参数openTimeout、resetTimeout和delegate
        // CompositeRetryPolicy：组合重试策略，有两种组合方式，乐观组合重试策略是指只要有一个策略允许即可以重试，悲观组合重试策略是指只要有一个策略不允许即可以重试，但不管哪种组合方式，组合中的每一个策略都会执行
        // ExceptionClassifierRetryPolicy：设置不同异常的重试策略，类似组合重试策略，区别在于这里只区分不同异常的重试
        // NeverRetryPolicy：只允许调用RetryCallback一次，不允许重试
        // SimpleRetryPolicy：固定次数重试策略，默认重试最大次数为3次，RetryTemplate默认使用的策略
        // TimeoutRetryPolicy：超时时间重试策略，默认超时时间为1秒，在指定的超时时间内允许重试
        // 设置重试策略
        SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
        // 最大重试次数，总调用次数
        retryPolicy.setMaxAttempts(3);
        retryTemplate.setRetryPolicy(retryPolicy);

        // 设置退避策略
        // ExponentialBackOffPolicy：指数退避策略，需设置参数sleeper、initialInterval、maxInterval和multiplier，initialInterval指定初始休眠时间，默认100毫秒，maxInterval指定最大休眠时间，默认30秒，multiplier指定乘数，即下一次休眠时间为当前休眠时间*multiplier
        // ExponentialRandomBackOffPolicy：随机指数退避策略，引入随机乘数可以实现随机乘数回退
        // FixedBackOffPolicy：固定时间的退避策略，需设置参数sleeper和backOffPeriod，sleeper指定等待策略，默认是Thread.sleep，即线程休眠，backOffPeriod指定休眠时间，默认1秒
        // NoBackOffPolicy：无退避算法策略，每次重试时立即重试
        // UniformRandomBackOffPolicy：随机时间退避策略，需设置sleeper、minBackOffPeriod和maxBackOffPeriod，该策略在[minBackOffPeriod,maxBackOffPeriod之间取一个随机休眠时间，minBackOffPeriod默认500毫秒，maxBackOffPeriod默认1500毫秒
        // FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy();
        // fixedBackOffPolicy.setBackOffPeriod(2000L);
        // retryTemplate.setBackOffPolicy(fixedBackOffPolicy);

        // initialInterval：初始休眠时间，默认100毫秒
        // multiplier：指定乘数，当前休眠时间*multiplier即为下一次的休眠时间；
        // maxInterval：指定最大休眠时间，默认30秒，避免multiplier过大引起无限期等待。
        ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
        // 最大重试3次，时间间隔为3秒，3*2秒，3*2*2秒，且时间间隔不大于15秒。
        backOffPolicy.setInitialInterval(3000L);
        backOffPolicy.setMultiplier(2D);
        backOffPolicy.setMaxInterval(15000L);
        retryTemplate.setBackOffPolicy(backOffPolicy);

        // 设置监听器，open和close分别在启动和结束时执行一次
        RetryListener[] listeners = {
                new RetryListener() {
                    @Override
                    public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
                        log.info("open");
                        return true;
                    }

                    @Override
                    public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
                        log.info("close");
                    }

                    @Override
                    public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
                        log.error("onError");
                    }
                }
        };
        retryTemplate.setListeners(listeners);

        return retryTemplate;
    }

    /**
     * 注解调用
     */
    @Bean
    public RetryListener retryListener() {
        return new RetryListener() {
            @Override
            public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
                log.info("open context = " + context + ", callback = " + callback);
                // 返回true继续执行后续调用
                return true;
            }

            @Override
            public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback,
                                                       Throwable throwable) {
                log.info("close context = " + context + ", callback = " + callback);
            }

            @Override
            public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback,
                                                         Throwable throwable) {
                log.error("onError context = " + context + ", callback = " + callback);
            }
        };
    }

}
